Avoid prop drilling in mapped components - javascript

Im mapping over some data and returning a NestedComponentOne each time. A nested child component NestedComponentThree needs access to a restaurant. How can I avoid prop drilling here? Wrapping every NestedComponentOne in a provider seems wrong.
const DATA = ["Restaurant 1"," Resto 2", "resto 3"]
export default DATA
import data from './data'
export default function App() {
return (
<div className="App">
{data.map((restaurant) => {
return <NestedComponentOne/>
})}
</div>
);
}
const NestedComponentOne = () => <div><NestedComponentTwo/></div>
const NestedComponentTwo = () => <div><NestedComponentThree/></div>
// I need access to restaurant
const NestedComponentThree = () => <div>restaurant</div>

The React doc recommends component composition:
Context is primarily used when some data needs to be accessible by many components at different nesting levels. Apply it sparingly because it makes component reuse more difficult.
If you only want to avoid passing some props through many levels, component composition is often a simpler solution than context.
So, you could for example do the following:
const data = ['Restaurant 1', ' Resto 2', 'resto 3'];
export default function App() {
return (
<div className="App">
{data.map((restaurant) => {
return (
<NestedComponentOne>
<NestedComponentTwo>
<NestedComponentThree restaurant={restaurant} />
</NestedComponentTwo>
</NestedComponentOne>
);
})}
</div>
);
}
const NestedComponentOne = ({ children }) => <div>{children}</div>;
const NestedComponentTwo = ({ children }) => <div>{children}</div>;
// Now you have access in this component to restaurant
const NestedComponentThree = ({ restaurant }) => <div>{restaurant}</div>;
Another way to do this is by passing the component as a prop:
const data = ['Restaurant 1', ' Resto 2', 'resto 3'];
export default function App() {
return (
<div className="App">
{data.map((restaurant) => {
return (
<NestedComponentOne
componentTwo={
<NestedComponentTwo
componentThree={
<NestedComponentThree restaurant={restaurant} />
}
/>
}
/>
);
})}
</div>
);
}
const NestedComponentOne = ({ componentTwo }) => <div>{componentTwo}</div>;
const NestedComponentTwo = ({ componentThree }) => <div>{componentThree}</div>;
// Now you have access in this component to restaurant
const NestedComponentThree = ({ restaurant }) => <div>{restaurant}</div>;

Related

How can i place values of mapped input element in a state array on the parent element

I have two components, the parent(name Parent) and child(name Child), on the parent, i map an array and render the child, so the child appears like 4 times(number of times the child is displayed based on the mapping), i have an input field on the Child component (which will be 1 input field for the rendered child component), i am basically trying to get the values of the input field from all the rendered Child component (4 been rendered based on the mapping) and then send it to my parent component (store it in a state on the parent component).
mock code
parent component
const Items = [1,2,3,4]
export const Parent= () => {
return (<div>
{Items.map((Item, index) => {
return (
<div key={index}>
<Child />
</div>
);
})}
</div>
)
}
child component
export const Child = () => {
const [Amount, setAmount] = useState();
return (
<input
value={Amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="Amount"
/>
)
}
sorry for the bad code formatting.
This is a mock of what it looks like
this should give a somewhat clear understanding or image of the issue, i basically want to get all the Amount on the 4 render children, place it in an array and send it to the Parent component (so i can call a function that uses all the amount in an array as an argument)
i tried to set the values of the Child component to a state on context (it was wrong, it kept on pushing the latest field values that was edited, i am new to react so i didnt understand some of the things that were said about state lifting
Congratulations, you've discovered the need for the Lifting State Up React pattern. Lift the "amount" state from the child component up to the parent component. The parent component holds all the state and provides it and a callback function down to children components via props.
Example:
import { useState } from "react";
import { nanoid } from "nanoid";
const initialState = Array.from({ length: 4 }, (_, i) => ({
id: nanoid(),
value: i + 1
}));
const Child = ({ amount, setAmount }) => {
return (
<input
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="Amount"
/>
);
};
const Parent = () => {
const [items, setItems] = useState(initialState);
const setAmount = (id) => (amount) =>
setItems((items) =>
items.map((item) =>
item.id === id
? {
...item,
value: amount
}
: item
)
);
return (
<div>
{items.map((item) => (
<Child
key={items.id}
amount={item.value}
setAmount={setAmount(item.id)}
/>
))}
</div>
);
};
try following method:
const Items = [1, 2, 3, 4];
export default function Parent() {
return <Child items={Items} />;
}
export function Child(props) {
const [childItems, setChildItems] = useState(props.items);
const handleChange = (e, index) => {
let temp = childItems;
temp[index] = e.target.value;
setChildItems(temp);
};
return (
<div>
{childItems.map((item, index) => {
return (
<div key={index}>
<input
value={item}
onChange={(e) => handleChange(e, index)}
placeholder="Amount"
/>
</div>
);
})}
</div>
);
}```

Passing components as state in React (Tab functionality)

Is it possible to pass other components through a state? I'm trying to make a tab function like a web browser, and if the user clicks the tab, a component shows up.
In my app.js I have -
const[chosenTab, setChosenTab] = useState("")
return (
<>
<Main chosenTab = {chosenTab}/>
</>
);
In Main.js -
const Main = ({chosenTab}) => {
return (
<>
{chosenTab}
</>
)
}
With the code below, the logic works to display the name of the tab/other component, but doesn't work if I replace {chosenTab} with <{chosenTab}/> to pass it as a component rather than just html.
I don't think this would work as you've structured it - I'd be welcome to have someone prove me wrong though since that would be a neat trick.
Now if I had to solve this problem, I'd simply use a object to hold what I need:
const tabMap = {
"string1": <component1 />,
"string2": <component2 />,
"string3": <component3 />
}
const Main = ({chosenTab}) => {
return (
<>
{tabMap[chosenTab]}
</>
)
}
Even further, let's say you wanted to pass in custom props, you could make tabMap a function to do that.
You can pass component reference itself as a tab.
const TabA = () => <div>Tab A</div>
const TabB = () => <div>Tab B</div>
const Main = ({ ChosenTab }) => {
retur <ChosenTab />
}
const App = () => {
const [chosenTab, setChosenTab] = useState(() => TabA);
const changeTab = (tab) => setChosenTab(() => tab);
return <Main ChosenTab={chosenTab} />
}
export default App;
Or you can store your tabs in object, Map or Array and set state accordingly
const tabs = {
A: TabA,
B: TabB
}
const App = () => {
const [chosenTab, setChosenTab] = useState(() => tabs.A);
const changeTab = (tabKey) => setChosenTab(() => tabs[tabKey]);
return <Main ChosenTab={chosenTab} />
}
export default App;

Get value from response and transfer to another component in React

I have this handleSubmit that returns me a key (verifyCode) that I should use in another component. How can I pass this verifyCode to another component?
const SendForm = ({ someValues }) => {
const handleSubmitAccount = () => {
dispatch(createAccount(id, username))
.then((response) => {
// I get this value from data.response, its works
const { verifyCode } = response;
})
.catch(() => {
});
};
return(
//the form with handleSubmitAccount()
)
}
export default SendForm;
The other component is not a child component, it is loaded after this submit step. But I don't know how to transfer the const verifyCode.
This is the view where the components are loaded, it's a step view, one is loaded after the other, I need to get the const verifyCode in FormConfirmation
<SendForm onSubmit={handleStepSubmit} onFieldSubmit={handleFieldSubmit} />
<FormConfirmation onSubmit={handleStepSubmit} onFieldSubmit={handleFieldSubmit} />
Does anyone know how I can do this?
You need to move up the state to a component that has both as children and then pass down a function that updates as a prop
import React from "react";
export default function App() {
const [value, setValue] = React.useState(0);
return (
<div className="App">
<Updater onClick={() => setValue(value + 1)} />
<ValueDisplay number={value} />
</div>
);
}
const Updater = (props) => <div onClick={props.onClick}>Update State</div>;
const ValueDisplay = (props) => <div>{props.number}</div>;
Check out the docs here
For more complex component structures or where your passing down many levels you may want to look into reactContext
import React from "react";
//Set Default Context
const valueContext = React.createContext({ value: 0, setValue: undefined });
export default function App() {
const [value, setValue] = React.useState(0);
return (
<div className="App">
{/** Pass in state and setter as value */}
<valueContext.Provider value={{ value: value, setValue }}>
<Updater />
<ValueDisplay />
</valueContext.Provider>
</div>
);
}
const Updater = () => {
/** Access context with hook */
const context = React.useContext(valueContext);
return (
<div onClick={() => context.setValue(context.value + 1)}>Update State</div>
);
};
const ValueDisplay = () => {
/** Access context with hook */
const context = React.useContext(valueContext);
return <div>{context?.value}</div>;
};

How to access history prop from a parent component using react?

i have a method showInfo that is accessed by two components Child1 and Child2.
initially i had the showInfo method within Child1 component like below
const Child1 = ({history}: RouteComponentProps) => {
type Item = {
id: string,
name: string,
value: string,
};
const showInfo = (item: item) => {
const id = item.id;
const value = item.value;
const handleRouteChange = () => {
const path = value === 'value1' ? `/value1/?item=${itemId}` : `/value2/?item=${itemId}`;
history.push(path);
}
return (
<Button onClick={handleRouteChange}> Info </Button>
);
}
return (
<SomeComponent
onDone = {({ item }) => {
notify({
actions: showInfo(item)
})
}}
/>
);
}
the above code works. but now i have another component child2 that needs to use the same method showInfo.
the component child2 is like below
const Child2 = () => {
return (
<Component
onDone = {({ item }) => {
notify({
actions: showInfo(item)
})
}}
/>
);
}
Instead of writing the same method showInfo in Child2 component i thought of having it in different file from where child1 and child2 components can share the method showInfo.
below is the file with name utils.tsx that has showInfo method
export const showInfo = (item: item) => {
const id = item.id;
const value = item.value;
const handleRouteChange = () => {
const path = value === 'value1' ? `/value1/?item=${itemId}` :
`/value2/?item=${itemId}`;
history.push(path); //error here push is not identified as no
//history passed
}
return (
<Button onClick={handleRouteChange}> Info </Button>
);
}
return (
<SomeComponent
onDone = {({ item }) => {
notify({
actions: showInfo(item)
})
}}
/>
);
}
With the above, i get the error where i use history.push in showInfo method. push not defined.
this is because history is not defined in this file utils.tsx
now the question is how can i pass history from child1 or child2 components. or what is the other way that i can access history in this case.
could someone help me with this. thanks.
EDIT:
notify in child1 and child2 is coming from useNotifications which is like below
const useNotifications = () => {
const [activeNotifications, setActiveNotifications] = React.useContext(
NotificationsContext
);
const notify = React.useCallback(
(notifications) => {
const flatNotifications = flatten([notifications]);
setActiveNotifications(activeNotifications => [
...activeNotifications,
...flatNotifications.map(notification => ({
id: notification.id,
...notification,
});
},
[setActiveNotifications]
)
return notify;
}
While you could potentially create a custom hook, your function returns JSX. There's a discussion about returning JSX within custom hooks here. IMO, this is not so much a util or custom hook scenario, as it is a reusable component. Which is fine!
export const ShowInfo = (item: item) => { // capitalize
const history = useHistory(); // use useHistory
// ... the rest of your code
}
Now in Child1 and Child2:
return (
<SomeComponent
onDone = {({ item }) => {
notify({
actions: <ShowHistory item={item} />
})
}}
/>
);
You may have to adjust some code in terms of your onDone property and notify function, but this pattern gives you the reusable behavior you're looking for.

React - Sharing state and methods between sibiling components

This is somewhat of a design or react patterns related question but I'm trying to figure out the best way to share sate and methods across different children.
I have a small app where the navigation from step to step and actual form data are rendered and handled in different sibling components. This is a codesandbox of roughly how the app functions.
What I was trying to figure out is the best way to share state between the sibling components. For instance, in the app linked above I need to validate the input from <AppStepOne /> when next is clicked and then move to <AppStepTwo />. Ideally I don't want to just have all the state live in the top level ExampleApp because there are a decent amount of steps and that can get ugly really fast.
The other though I had which I wanted to get some input on what using the react context api. I haven't worked with it before so I wanted to get some idea as if it's something that could possible work as clean solution for this.
Code for app above:
const ExampleApp = () => {
const [currentStep, setCurrentStep] = useState(1);
const getCurrentAppStep = () => {
switch (currentStep) {
case 1:
return {
app: <AppStepOne />,
navigation: (
<AppNavigation onNext={() => setCurrentStep(currentStep + 1)} />
)
};
case 2:
return {
app: <AppStepTwo />,
navigation: (
<AppNavigation onNext={() => setCurrentStep(currentStep + 1)} />
)
};
default:
return {
app: <AppStepOne />,
navigation: (
<AppNavigation onNext={() => setCurrentStep(currentStep + 1)} />
)
};
}
};
const myAppStep = getCurrentAppStep();
return (
<div>
<ParentComp>
<ChildOne>{myAppStep.app}</ChildOne>
<ChildTwo>{myAppStep.navigation}</ChildTwo>
</ParentComp>
</div>
);
};
const ParentComp = ({ children }) => {
return <div>{children}</div>;
};
const ChildOne = ({ children }) => {
return <div>{children}</div>;
};
const ChildTwo = ({ children }) => {
return <div>{children}</div>;
};
const AppStepOne = () => {
const [name, setName] = useState("");
return (
<div>
Name: <input onChange={(e) => setName(e.target.value)} />
</div>
);
};
const AppStepTwo = () => {
const [zipcode, setZipCode] = useState("");
return (
<div>
Zipcode: <input onChange={(e) => setZipCode(e.target.value)} />
</div>
);
};
const AppNavigation = ({ onNext }) => {
return <button onClick={onNext}>Next</button>;
};

Categories

Resources