1

I've been playing with this pen for awhile as an exercise with various React hooks. I'm wanting to dynamically render a list of names from a context, and for that list to re-render after posting a new person object to the context array.

It seems I've gotten close, as submitting the form logs the updated peopleNameList however the context doesn't appear to be updating properly, and the form submission is crashing the pen. Am I missing something small, or am I missing a larger composition issue?

const {createContext, useState, useEffect, useContext, useReducer} = React;

// https://stackoverflow.com/questions/55757761/handle-an-input-with-react-hooks

// https://upmostly.com/tutorials/how-to-use-the-usecontext-hook-in-react

var dataStore = {
  people: [
         {
            firstName: 'Jim',
            lastName: 'Smith',
            email: 'jims@email.com',
            age: 32,
            job: 'Engineer',
            transportation: 'car'
         },
         {
             firstName: 'Sarah',
             lastName: 'Alton',
             email: 'salton@email.com',
             age: 24,
             job: 'Programmer',
             transportation: 'bike'
         },
        {
             firstName: 'Leslie',
             lastName: 'Leeland',
             email: 'lleeland@woohoo.com',
             age: 24,
             job: 'Teacher',
             transportation: 'Scooter'
         }
    ]
};


// define that context takes an object and an update function
const PeopleContext = createContext();
const PeopleContextProvider = (props) => {
    // dataStore.people as default context state, with a reducer dispatch returned for use with useContext hook to add a new person from the form
    // note you cannot push to a state array because this mutates state directly
    // https://medium.com/javascript-in-plain-english/how-to-add-to-an-array-in-react-state-3d08ddb2e1dc
    const [peopleStore, addNewPerson] = useReducer((peopleStore, newPerson) =>
     peopleStore.concat(newPerson), dataStore.people);

    return(
        // Context Provider takes a value prop
        <PeopleContext.Provider value={[peopleStore, addNewPerson]}>
            {props.children}
        </PeopleContext.Provider>
    )
}

function RecentPeople(props) {
    // peopleStore equivalent to value prop of PeopleContext provider
    const [peopleStore, addNewPerson] = useContext(PeopleContext);

    const addToPeopleNameList = () => {
        let peopleNameList = [];
        _.map(peopleStore, (i) => {
            let name = `${i.firstName} ${i.lastName}`;
                peopleNameList.push(name);
            return peopleNameList;
        });
        console.log(`peopleNameList: ${peopleNameList}`);
        return(<>
                    {_.map(peopleNameList, (name, i) => <li key={i}>{name}</li>)}
                </>)
    };

    useEffect(() => addToPeopleNameList(), [peopleStore]);

    return(
        <>
        <h3>Recent People</h3>
        <ul>
            {addToPeopleNameList()}
        </ul>
        </>
    );
} // end RecentPeople

function Input(props) {
    let {inputId, value, handleChange } = props;
    let inputLabel = _.startCase(props.inputId);
    return(
        <div class="input-group">
            <label for={inputId}>{inputLabel}</label>
            <input id={inputId} value={value} name={inputId} onChange={handleChange} />
        </div>
    );
};

const inputFields = [ 'firstName', 'lastName', 'email', 'age', 'job', 'transportation' ]

const initialFormState = {
    firstName: '',
    lastName: '',
    email: '',
    age: null,
    job: '',
    transportation: ''
};

function NewUserForm(props) {
    // pass in the reducer function inline to concat existing and new state
    const [inputValues, setInputValues] = useReducer((state, newState) => ({...state, ...newState}), initialFormState);

    const [peopleStore, addNewPerson] = useContext(PeopleContext);

    function handleOnChange(event) {
     const {name, value} = event.target;
            // call the reducer update function, passing in existing formstate with state of current input
            setInputValues({...inputValues, [name]: value });
    }

    function handleSubmit(event) {
        event.preventDefault();
        addNewPerson(inputValues);
        console.log(peopleStore);
        setInputValues({}, {...initialFormState});
    }

    // don't pass event here
    function handleReset(props) {
        setInputValues({}, {...initialFormState});
    }

    return(
        <form id="new-user-form" onSubmit={handleSubmit}>
            {_.map(inputFields, (fieldName) => (<Input key={fieldName} type="text" name={fieldName} inputId={fieldName} handleChange={handleOnChange} />))}

            <button type="submit" value="submit">Add User</button>

            <button type="reset" style={{"marginLeft": "20px"}} onClick={handleReset}>Reset Form</button>
        </form>
    );
} // form


function App() {
    return(
        <PeopleContextProvider>
        <div id="main">
            <h1>Hello!</h1>
            <NewUserForm />
            <RecentPeople />
        </div>
        </PeopleContextProvider>
    );
};

ReactDOM.render(<App />, document.getElementById('App'));

1 Answers1

0

It appears the major culprit was useEffect can't return the render list function directly, and wrapping it in brackets was required to scope the function. With a couple other name changes and small tweaks, the corrected pen is here and now shows the context's dispatch function correctly being called and the list component listening for the change and rerendering the list. Probably some optimization that could be done there to only show differing items.

https://codepen.io/smallreflection/pen/yLyLQge?editors=1011

useEffect(() => { renderPeopleNameList() }, [peopleStoreContext]);