Svelte: update parent state from child - javascript

In react I can do something like:
App.jsx
const App = () => {
const [state, setState] = useState("old value")
return (
<>
<ChildComponent setState={setState} />
</>
)
}
ChildComponent.jsx
const ChildComponent = ({ setState }) => {
const changeState = () => setState("new value")
return (
<div>
<button onClick={changeState}>Click</button>
</div>
)
}
Then the parent state will be updated.
I don't know how to do the same in Svelte...
I have this:
index.svelte
<script>
import { ChildComponent } from "#components"
let state = "old value"
</script>
<main>
<ChildComponent {state} />
</main>
ChildComponent.svelte
<script>
export let state
const changeState = () => {
// I need to do something like:
state = "new value"
}
</script>
<div>
<button on:click={changeState}>Click</button>
</div>
And see the new value reflected in the parent.
I wanna to do it without use stores... I don't know if it's possible.
Maybe store is the only way to proceed.
I'm ears

There are two ways of doing this in Svelte:
With two-way binding
This will create a sort of connection between parent and child, where updating one will automatically update the other.
<!-- Parent.svelte -->
<script>
import Child from './Child.svelte';
let state = "initial";
</script>
<Child bind:state />
<!-- Child.svelte -->
<script>
export let state;
function changeState() {
state = "new value";
</script>
<button on:click={changeState}>Click</button>
Using events
Just like props go down, events are used to pass informaton up. This can be used if you don't want a strict equivalence between the two states and is a touch more versatile (but also more verbose).
<!-- Parent.svelte -->
<script>
import Child from './Child.svelte';
let state = "initial"
function handleChange(ev) {
state = ev.detail.state
}
</script>
<Child {state} on:change={handleChange} />
<!-- Child.svelte -->
<script>
import { createEventDispatcher } from 'svelte';
export let state
const dispatch = createEventDispatcher()
function changeState() {
// first argument is the event name
// second is an object placed in ev.detail
dispatch('change', { state: "new value" });
}
</script>
<button on:click={changeState}>Click</button>
Which of these to use is up to you and likely depends on the situation, but both benefit that they do not actively rely on what is happening outside the component. If nobody used bind the component doesn't care, it still works. If nobody is listening to this event, again the component doesn't care and keeps on working. Contrast this to the situation where no function is passed, or the function has the wrong signature, suddenly your component is dependent on something that it cannot control which is not a good pattern.

Related

Exporting a variable set in a function to another component React

I have some variables that are set within a function in one component of my react application that I need to reuse in other components.
I set the variables in component 1 like so (this is a much simplified version but captures the structure)
export default function Example() {
const [market, setMarket] = useState('');
return (
<button onClick={setMarket('1')}>Click 1</button>
<button onClick={setMarket('2')}>Click 2</button>
<button onClick={setMarket('3')}>Click 3</button> )}
How can I export the 'market' variable specifically, so that I can import it into another component (in a separate jsx file) and render as necessary. I know that I can just import the whole component, or set a variable outside of this function in component 1 and export it but I do not know how I would then conditionally set it based on which button is clicked.
Thank you
Hey #Milo there are different ways to use state value in another component.
First is props-
Create a component that passes values like-
const passValue = () => {
const [ value, setValue ] = useState("")
return (
)
}
While in the second component we get the value like-
const SecondComponent = ({value})=>{
return(
<div>
{value}
</div>
)
}
While Second method is to pass value using state and get it by useLocation in another component-
First Component like-
const FirstComponent = () =>{
return(
<div>
<Link to="/secondpage" state={{value:yourValue/state}}>Click Here</Link>
</div>
)
}
Second Component Like-
const Second Component = () => {
const {state} = useLocation()
return(
<div>{state}</div>
)
}
Hope these solution helps to solve your problem. If you still facing issue lemme know, i will help you.
Thanks

how to pass an event from a child component to parent component back down to another child in React

Lets say for instance that I have three components in React, an App (the parent component), a button component and a component that is meant to display something, can be anything doesn't really matter. Lets say in the button component is activated, how would I pass the information (ie that the event actually happened) to the App parent component back down to the other child component to let it know a specific event happened to display some message?
this is how I would go about dong this using hooks :
const Parent=(props)=>{
[eventHappend,setEventHappend]=useState(false)
return (
<div>
<Child1 setEventHappend={setEventHappend} />
<Child2 eventHappend={eventHappend} />
</div>
)
}
const Child =({setEventHappend})=>{
return (
<div>
<button onClick={e=>setEventHappend(true)} > click me 1 </button>
</div>
)
}
const Child2 =({eventHappend})=>{
return (
<div>
<button onClick={e=>{/* some code*/ }} > {eventHappend?'event happend':'event didnt happen yet '} </button>
</div>
)
}
There are various ways you can achieve this pass state as props to the child elements (must know before other methods), context or use redux which has a store.
Generally speaking. React has one way data flow, uni directional. As in the parent will hold the state and will be passed to child elements.
Here App holds the state buttonClick which has the information about the event.
const App = () => {
const [ buttonClick, setButtonClick] = React.useState(false);
const messageToBeDispalyed = "The button has been clicked"
return (
<div>
<CustomButton setEventHappened={setButtonClick} />
<DisplayText value = {buttonClick ? messageToBeDispalyed : ""} />
</div>
)
}
const CustomButton = (props) =>{
return <button onClick={(e)=>props.setEventHappened(true)} > Click Me </button>
}
const DisplayText = (props) => {
return <h1> {props.value} </h1>
}
Similar answers to the others, but you would pass down a method to the child from the parent to update the state. But be aware that by doing this will cause a rerender for all of the parent's children.
const Parent = () => {
const [state, setState] = React.useState(false);
const handleClick = value => {
setState(value);
};
return (
<Child state={state} handleClick={handleClick} />
<OtherChild isTrue={state} /> // this component needs data changed by <Child />
)
};
const Child = props => {
const {state, handleClick} = props;
return (
<button onClick={() => handleClick(!state)} >click me</button>
);
};
This way the parent alone handles the state change and provides that method to the child.
as #Loveen Dyall and #Shruti B mentioned you can use RXJS for a more modular approach ,While RxJS is typically thought of as being used with Angular projects, it's a completely separate library that can be used with other JavaScript frameworks like React and Vue.
When using RxJS with React, the way to communicate between components is to use an Observable and a Subject (which is a type of observable), I won't go too much into the details about how observables work here since it's a big subject, but in a nutshell there are two methods that we're interested in: Observable.subscribe() and Subject.next().
learn more about RXJS and Observables : https://blog.logrocket.com/understanding-rxjs-observables/
Observable.subscribe()
The observable subscribe method is used by React components to subscribe to messages that are sent to an observable.
Subject.next()
The subject next method is used to send messages to an observable which are then sent to all React components that are subscribers (a.k.a. observers) of that observable.
here is how you implement it in this use case :
this is called a service and you would put this file in a services folder
import { Subject } from 'rxjs';
const subject = new Subject();
//here where sending { event: eventTitle } , that way you can listen to diffrent events , for example 'INCREMENTED' you could even send values
export const eventsService= {
sendEvent: eventTitle => subject.next({ title: eventTitle }),
getEventNotification: () => subject.asObservable()
};
in your Child 1 component you would subscribe to the observable in useEffect or compoentDidMount if your using class component:
import { eventsService} from '../services';
const Child1 =()=>{
const [child2EventFired,setChild2EventFired]=useState(false)
useEffect(()=>{
let subscription = eventsService.getEventNotification().subscribe(eventTitle =>
{
if (eventTitle=="CHILD2_BUTTON_CLICK" ) {
setChild2EventFired(true)
}else{
setChild2EventFired(false)
}
});
return ()=>{
subscription.unsubscribe();
}
},[])
return <div>
<button> {child2EventFired? 'child2 button event fired':'event not fired yet'} </button>
</div>
}
in your Child 2 component
import { eventsService} from '../services';
const Child2 =()=>{
Child2Click=(e)=>{
//some code,
//then send messages to the observable observable
eventsService.sendEvent('CHILD2_BUTTON_CLICK');
}
return <div>
<button onClick={Child2Click} >click me</button>
</div>
}

React on click event order array of data passing in the component

I'm new to React and I'd like some help please. I'm having a button and a component inside my app.js which is the main file
import React from 'react'
const App = () => {
const {data, loading, error} = useQuery(GET_DATA, {
variables: {...}
})
console.log(data)
state = {
clickSort: false
}
let clickSort = () => {
this.setState({
clickSort: true
})
}
return (
<div className="myApp">
<button onClick="{this.clickSort}">Click Me</button>
<div className="myClass">
<FooComponent fooData={data} clickSort={this.state.clickSort} />
</div>
</div>
)
}
What I want to do is when I click the button to sort the array of data I'm rendering in my component in a desc order. I was thinking of passing another parameter like a flag in the component, but I'm not sure how can I do this
If both of your components (<Button /> and <List />) are wrapped within common parent (<Parent />) you may employ the concept, known as lifting state up
Essentially, it is binding event handler within one of the child component's props (onSort() of <Button />) to the callback within parent (handleSort() of <Parent />), as well as binding dependent child prop (isSorted of <List />) to the state variable of common parent (sorted of <Parent />).
With that, you simply keep track of sorted flag within parent state (using useState() hook) and once handleSort() is triggered, it modifies that flag and consequent re-render of dependent components (<List />) takes place:
const { render } = ReactDOM,
{ useState } = React
const sampleData = ['itemC', 'itemA', 'itemD', 'itemB']
const Button = ({onSort}) => <button onClick={onSort}>Sort it</button>
const List = ({listData, isSorted}) => {
const listToRender = isSorted ? listData.sort((a,b) => b > a ? 1 : -1) : listData
return (
<ul>
{listToRender.map((li,key) => <li {...{key}}>{li}</li>)}
</ul>
)
}
const Parent = () => {
const [sorted, setSorted] = useState(false),
handleSort = () => setSorted(true)
return (
<div>
<Button onSort={handleSort} />
<List listData={sampleData} isSorted={sorted} />
</div>
)
}
render (
<Parent />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
It looks from your question that you want to let a child component (FooComponent) know that the button has been clicked so that it can process (sort) the data it has received.
There are a lot of approaches to this. For instance, you could pass a boolean property to the child component that is a flag for it to do the sorting. So the parent component tracks when the button has been clicked, and the child component just observes this (perhaps in componentDidUpdate).
This would change slightly if you are using functional components, rather than class based components, but it gives you an idea.
state = {
requestSort: false
}
requestSort = () => {
this.setState({
requestSort: true
}
}
render() {
return (
<>
<button id="myBtn" onClick={this.requestSort}>Click Me</button>
<div className="myClass">
<FooComponent requestSort={this.state.requestSort} fooData={data} />
</div>
</>
)
}
Alternatively, since the data is being passed to the child component as well, you could have the parent sort it when it is clicked. It depends on if you are doing anything else with the data (i.e. is only FooComponent the one that should have the sorted copy of the data or not).
Pass the data from the state into FooComponent and write a function that sorts the data in that state. The data will instantly be updated in the child component once the state has updated in the parent component because the child component will rerender once it's noticed that the data in the parent component doesn't match the data that it previously received. Below is an example.
import React from 'react'
const FooComponent = ({ fooData }) => (
<div>
{fooData}
</div>
)
export default class Home extends React.Component {
constructor(props){
super(props);
this.state = {
data: [1, 4, 2, 3]
}
}
sortData() {
const { data } = this.state;
this.setState({
data: data.sort((a, b) => b - a),
})
}
render(){
const { data } = this.state;
return (
<div>
<button id="myBtn" onClick={() => this.sortData()}>Click Me</button>
<div className="myClass">
<FooComponent fooData={data} />
</div>
</div>
)
}
}

Is there any way to add an event listener in a method in a component A, and have the target in a separate component

I want to add an event listener to the component Table so that every time I save a form in the component Form by the method saveForm() I call a method called showData() in the component Table.
Form Component
let persons = [];
if (JSON.parse(localStorage.getItem("personsForms")) !== null)
persons = JSON.parse(localStorage.getItem("personsForms"));
saveForm(myForm) {
persons.push(myForm);
localStorage.setItem("personsForms", JSON.stringify(persons));
}
Table Component
let persons;
let localStoragePersons = JSON.parse(localStorage.getItem("personsForms"));
persons = localStoragePersons !== null ? localStoragePersons : [];
showData() {
let table = document.getElementById('editableTable');
let x = table.rows.length;
while (--x) {
table.deleteRow(x);
}
let i = 0;
...........
}
Assuming that Table component is the parent component of Form component.
Pass the method as a prop to Form Component and call it accessing to the props (this.props.parentMethod())
From React DOCS:
Remember: React is all about one-way data flow down the component hierarchy. It may not be immediately clear which component should own what state. This is often the most challenging part for newcomers to understand, so follow these steps to figure it out:
React's one-way data flow is meant to be top-down, parent-child. The way you're suggesting would be sibling-to-sibling, which would be side-to-side instead of top-down. This is an anti-pattern.
You can set up your component hierarchy like this:
See it that works for you.
function App() {
function doSomethingApp(formValue) {
console.log('Do something from App!');
console.log('Form value is: ' + formValue);
}
return(
<React.Fragment>
<Table/>
<Form
doSomethingApp={doSomethingApp}
/>
</React.Fragment>
);
}
function Table(props) {
function doSomethingTable(formValue) {
console.log('Do something from Table!');
console.log('Form value is: ' + formValue);
}
return(
<div>I am Table</div>
);
}
function Form(props) {
const [value,setValue] = React.useState('');
return(
<React.Fragment>
<div>I am Form</div>
<input type='text' value={value} onChange={()=>setValue(event.target.value)}/>
<div>
<button onClick={()=>props.doSomethingApp(value)}>Do something from App</button>
</div>
</React.Fragment>
);
}
ReactDOM.render(<App/>,document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
SNIPPET: ATTENTION, THIS IS AN ANTI-PATTERN
Although I wouldn't recommend this at all. This is a working example with the anti-pattern you requested.
Sibling1 has a useEffect which updates a state on the common parent App with its method showData. Whenever showData changes, the effect is run again and keeps it updated. showData is a memoized callback. You can add a depedency array to update this memoized callback whenever some other variable changes.
Source: Hooks API Reference
function App() {
const [sibling1Method,setSibling1Method] = React.useState({method: null});
return(
<React.Fragment>
<Sibling1
setSibling1Method={setSibling1Method}
/>
<Sibling2
sibling1Method={sibling1Method}
/>
</React.Fragment>
);
}
function Sibling1(props) {
const showData = React.useCallback((formValue) => {
console.log('A click from Sibling2 is triggering a Sibling1 Method');
},[]);
React.useEffect(()=>{
props.setSibling1Method({method: showData});
},[showData]);
return(
<div>Sibling 1 </div>
);
}
function Sibling2(props) {
return(
<React.Fragment>
<span>Sibling 2 </span>
<button onClick={()=>props.sibling1Method.method()}>Click</button>
</React.Fragment>
);
}
ReactDOM.render(<App/>,document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

How to access a child's state in React

I have the following structure:
FormEditor - holds multiple instances of FieldEditor
FieldEditor - edits a field of the form and saving various values about it in its state
When a button is clicked within FormEditor, I want to be able to collect information about the fields from all FieldEditor components, information that's in their state, and have it all within FormEditor.
I considered storing the information about the fields outside of FieldEditor's state and put it in FormEditor's state instead. However, that would require FormEditor to listen to each of its FieldEditor components as they change and store their information in its state.
Can't I just access the children's state instead? Is it ideal?
Just before I go into detail about how you can access the state of a child component, please make sure to read Markus-ipse's answer regarding a better solution to handle this particular scenario.
If you do indeed wish to access the state of a component's children, you can assign a property called ref to each child. There are now two ways to implement references: Using React.createRef() and callback refs.
Using React.createRef()
This is currently the recommended way to use references as of React 16.3 (See the documentation for more information). If you're using an earlier version then see below regarding callback references.
You'll need to create a new reference in the constructor of your parent component and then assign it to a child via the ref attribute.
class FormEditor extends React.Component {
constructor(props) {
super(props);
this.FieldEditor1 = React.createRef();
}
render() {
return <FieldEditor ref={this.FieldEditor1} />;
}
}
In order to access this kind of ref, you'll need to use:
const currentFieldEditor1 = this.FieldEditor1.current;
This will return an instance of the mounted component so you can then use currentFieldEditor1.state to access the state.
Just a quick note to say that if you use these references on a DOM node instead of a component (e.g. <div ref={this.divRef} />) then this.divRef.current will return the underlying DOM element instead of a component instance.
Callback Refs
This property takes a callback function that is passed a reference to the attached component. This callback is executed immediately after the component is mounted or unmounted.
For example:
<FieldEditor
ref={(fieldEditor1) => {this.fieldEditor1 = fieldEditor1;}
{...props}
/>
In these examples the reference is stored on the parent component. To call this component in your code, you can use:
this.fieldEditor1
and then use this.fieldEditor1.state to get the state.
One thing to note, make sure your child component has rendered before you try to access it ^_^
As above, if you use these references on a DOM node instead of a component (e.g. <div ref={(divRef) => {this.myDiv = divRef;}} />) then this.divRef will return the underlying DOM element instead of a component instance.
Further Information
If you want to read more about React's ref property, check out this page from Facebook.
Make sure you read the "Don't Overuse Refs" section that says that you shouldn't use the child's state to "make things happen".
If you already have an onChange handler for the individual FieldEditors I don't see why you couldn't just move the state up to the FormEditor component and just pass down a callback from there to the FieldEditors that will update the parent state. That seems like a more React-y way to do it, to me.
Something along the line of this perhaps:
const FieldEditor = ({ value, onChange, id }) => {
const handleChange = event => {
const text = event.target.value;
onChange(id, text);
};
return (
<div className="field-editor">
<input onChange={handleChange} value={value} />
</div>
);
};
const FormEditor = props => {
const [values, setValues] = useState({});
const handleFieldChange = (fieldId, value) => {
setValues({ ...values, [fieldId]: value });
};
const fields = props.fields.map(field => (
<FieldEditor
key={field}
id={field}
onChange={handleFieldChange}
value={values[field]}
/>
));
return (
<div>
{fields}
<pre>{JSON.stringify(values, null, 2)}</pre>
</div>
);
};
// To add the ability to dynamically add/remove fields, keep the list in state
const App = () => {
const fields = ["field1", "field2", "anotherField"];
return <FormEditor fields={fields} />;
};
Original - pre-hooks version:
class FieldEditor extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
const text = event.target.value;
this.props.onChange(this.props.id, text);
}
render() {
return (
<div className="field-editor">
<input onChange={this.handleChange} value={this.props.value} />
</div>
);
}
}
class FormEditor extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.handleFieldChange = this.handleFieldChange.bind(this);
}
handleFieldChange(fieldId, value) {
this.setState({ [fieldId]: value });
}
render() {
const fields = this.props.fields.map(field => (
<FieldEditor
key={field}
id={field}
onChange={this.handleFieldChange}
value={this.state[field]}
/>
));
return (
<div>
{fields}
<div>{JSON.stringify(this.state)}</div>
</div>
);
}
}
// Convert to a class component and add the ability to dynamically add/remove fields by having it in state
const App = () => {
const fields = ["field1", "field2", "anotherField"];
return <FormEditor fields={fields} />;
};
ReactDOM.render(<App />, document.body);
As the previous answers said, try to move the state to a top component and modify the state through callbacks passed to its children.
In case that you really need to access to a child state that is declared as a functional component (hooks) you can declare a ref in the parent component, and then pass it as a ref attribute to the child, but you need to use React.forwardRef and then the hook useImperativeHandle to declare a function you can call in the parent component.
Take a look at the following example:
const Parent = () => {
const myRef = useRef();
return <Child ref={myRef} />;
}
const Child = React.forwardRef((props, ref) => {
const [myState, setMyState] = useState('This is my state!');
useImperativeHandle(ref, () => ({getMyState: () => {return myState}}), [myState]);
})
Then you should be able to get myState in the Parent component by calling:
myRef.current.getMyState();
It's 2020 and lots of you will come here looking for a similar solution but with Hooks (they are great!) and with the latest approaches in terms of code cleanliness and syntax.
So as previous answers had stated, the best approach to this kind of problem is to hold the state outside of child component fieldEditor. You could do that in multiple ways.
The most "complex" is with a global context (state) that both parent and children could access and modify. It's a great solution when components are very deep in the tree hierarchy and so it's costly to send props in each level.
In this case I think it's not worth it, and a more simple approach will bring us the results we want, just using the powerful React.useState().
An approach with a React.useState() hook - way simpler than with Class components
As said, we will deal with changes and store the data of our child component fieldEditor in our parent fieldForm. To do that we will send a reference to the function that will deal and apply the changes to the fieldForm state, you could do that with:
function FieldForm({ fields }) {
const [fieldsValues, setFieldsValues] = React.useState({});
const handleChange = (event, fieldId) => {
let newFields = { ...fieldsValues };
newFields[fieldId] = event.target.value;
setFieldsValues(newFields);
};
return (
<div>
{fields.map(field => (
<FieldEditor
key={field}
id={field}
handleChange={handleChange}
value={fieldsValues[field]}
/>
))}
<div>{JSON.stringify(fieldsValues)}</div>
</div>
);
}
Note that React.useState({}) will return an array with position 0 being the value specified on call (Empty object in this case), and position 1 being the reference to the function
that modifies the value.
Now with the child component, FieldEditor, you don't even need to create a function with a return statement. A lean constant with an arrow function will do!
const FieldEditor = ({ id, value, handleChange }) => (
<div className="field-editor">
<input onChange={event => handleChange(event, id)} value={value} />
</div>
);
Aaaaand we are done, nothing more. With just these two slim functional components we have our end goal "access" our child FieldEditor value and show it off in our parent.
You could check the accepted answer from 5 years ago and see how Hooks made React code leaner (by a lot!).
Hope my answer helps you learn and understand more about Hooks, and if you want to check a working example here it is.
Now you can access the InputField's state which is the child of FormEditor.
Basically, whenever there is a change in the state of the input field (child), we are getting the value from the event object and then passing this value to the Parent where in the state in the Parent is set.
On a button click, we are just printing the state of the input fields.
The key point here is that we are using the props to get the input field's id/value and also to call the functions which are set as attributes on the input field while we generate the reusable child input fields.
class InputField extends React.Component{
handleChange = (event)=> {
const val = event.target.value;
this.props.onChange(this.props.id , val);
}
render() {
return(
<div>
<input type="text" onChange={this.handleChange} value={this.props.value}/>
<br/><br/>
</div>
);
}
}
class FormEditorParent extends React.Component {
state = {};
handleFieldChange = (inputFieldId , inputFieldValue) => {
this.setState({[inputFieldId]:inputFieldValue});
}
// On a button click, simply get the state of the input field
handleClick = ()=>{
console.log(JSON.stringify(this.state));
}
render() {
const fields = this.props.fields.map(field => (
<InputField
key={field}
id={field}
onChange={this.handleFieldChange}
value={this.state[field]}
/>
));
return (
<div>
<div>
<button onClick={this.handleClick}>Click Me</button>
</div>
<div>
{fields}
</div>
</div>
);
}
}
const App = () => {
const fields = ["field1", "field2", "anotherField"];
return <FormEditorParent fields={fields} />;
};
ReactDOM.render(<App/>, mountNode);
You may access the child state by passing a callback to the child component.
const Parent = () => {
return (
<Child onSubmit={(arg) => {
console.log('accessing child state from parent callback: ', arg)
}}
/>
)
}
const Child = ({onSubmit}) => {
const [text, setText] = useState('');
return (
<>
<input value={text} onChange={setText}>
<button onClick={() => onSubmit(text)} />
</>
)
}
Now if you click the button in the child component, you will execute the function passed from the parent and have access to the child component's state variables.

Categories

Resources